﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;

namespace ProtocolCoder
{
    class Generator
    {
        private string output_directory_;
        private Hashtable variables_;
        private Hashtable message_info_from_name_;
        private OutputPrinter Printer;
        private bool IsServerProto;


        public Generator(string OutputDirectory,
                         Hashtable variables,
                         Hashtable MessageInfosFromName, bool ServerProto)
        {
            output_directory_ = OutputDirectory;
            variables_ = variables;
            message_info_from_name_ = MessageInfosFromName;
            IsServerProto = ServerProto;
        }

        /// <summary>
        /// 生成头文件
        /// </summary>
        /// <returns></returns>
        public bool GenerateHeaderFile()
        {
            if (!variables_.ContainsKey("FileName"))
            {
                Console.WriteLine("无法找到与输出文件名称相关的信息");
                return false;
            }
            try
            {
                string filename = output_directory_ + variables_["FileName"] + ".h";
                using (StreamWriter sw = new StreamWriter(filename, false, Encoding.GetEncoding("gb2312")))
                {
                    Printer = new OutputPrinter(sw, '$');
                    Printer.Print("/********************************************************************\n" +
                                  "**       This head file is generated by program,                   **\n" +
                                  "**            Please do not change it directly.                    **\n" +
                                  "**                   Auther: Fu Yifan                              **\n" +
                                  "********************************************************************/\n\n");

                    Printer.Print(variables_, "#ifndef $FILENAME$_H_INCLUDE_VERSION_$Version$\n" +
                                              "#define $FILENAME$_H_INCLUDE_VERSION_$Version$\n\n");

                    Printer.Print(variables_, "#include \"$InputFile$.pb.h\"\n\n");
                    Printer.Print(variables_, "class $ProcessorClass$;\n\n");
                    //Printer.Print("using namespace google::protobuf\n");
                    Printer.Print(variables_, "typedef void (*DeFunc$FactoryName$)(const void * buf, int nSize, $ProcessorClass$*);\n\n");

                    // generate enum declaration
                    Printer.Print(variables_, "enum $FileName$_MessageID\n{\n");
                    Printer.Indent();
                    int MaxIndex = Convert.ToInt32(variables_["MaxIndex"]);
                    ArrayList tempList = new ArrayList(MaxIndex + 1);
                    for (int i = 0; i < MaxIndex + 1; i++)
                        tempList.Add(null);
                    foreach (DictionaryEntry de in message_info_from_name_)
                    {
                        MessageInfo info = (MessageInfo)de.Value;
                        tempList[info.ID] = info;
                    }
                    for (int i = 0; i < MaxIndex + 1; i++)
                    {
                        if (tempList[i] != null)
                        {
                            MessageInfo info = (MessageInfo)tempList[i];
                            Printer.Print(String.Format("{0}\t\t\t\t\t= {1},\n", info.Name, info.ID));
                        }
                    }
                    Printer.Print(variables_, "$FileName$MaxMessageID\n");
                    Printer.Outdent();
                    Printer.Print("};\n\n");

                    // generate class declaration
                    Printer.Print(variables_, "class $FactoryName$\n" +
                                              "{\n" +
                                               "public:\n");
                    Printer.Indent();
                    Printer.Print(variables_, "$FactoryName$();\n" +
                                               "~$FactoryName$();\n" +
                                               "void Decode(int nMsgID, const void * buf, int nSize, $ProcessorClass$ * pProc);\n" +
                                               "bool Init();\n");
                    Printer.Outdent();
                    Printer.Print("private:\n");
                    Printer.Indent();
                    Printer.Print(variables_, "DeFunc$FactoryName$ m_DecodeFuncArray[$FileName$MaxMessageID];\n");
                    Printer.Outdent();
                    Printer.Print("};\n");
                    Printer.Print("#endif\n\n");
                }

                return true;
            }
            catch (System.Exception e)
            {
                Console.WriteLine(e.Message);
                return false;
            }
        }

        /// <summary>
        /// 生成cpp文件
        /// </summary>
        /// <returns></returns>
        public bool GenerateCPlusPlusFile()
        {
            if (!variables_.ContainsKey("FileName"))
            {
                Console.WriteLine("无法找到与输出文件名称相关的信息");
                return false;
            }

            try
            {
                string filename = output_directory_ + variables_["FileName"] + ".cpp";
                using (StreamWriter sw = new StreamWriter(filename, false, Encoding.GetEncoding("gb2312")))
                {
                    Printer = new OutputPrinter(sw, '$');
                    Printer.Print(variables_, "#include \"stdafx.h\"\n" +
                                  "#include \"$FileName$.h\"\n$Include$\n\n");
                    foreach (DictionaryEntry de in message_info_from_name_)
                    {
                        MessageInfo info = (MessageInfo)de.Value;
                        if (info.func == null)
                            continue;

                        Printer.Print(variables_, String.Format("void Decode{0}(const void * buf, int nSize, $ProcessorClass$ * pProc)\n", de.Key) + "{\n");
                        Printer.Indent();

                        if (IsServerProto == info.ByServer)
                        {
                            //info.
                            Printer.Print(String.Format("{0} pkg;\n", info.Type));
                            Printer.Print("if (!pkg.ParseFromArray(buf, nSize))\n");
                            Printer.Indent();
                            Printer.Print("return;\n");
                            Printer.Outdent();
                            Printer.Print("if (pProc){\n");
                            Printer.Indent();
                            Printer.Print(String.Format("pProc->{0}(&pkg);\n", info.func));
                            Printer.Outdent();
                            Printer.Print("}\n");
                        }
                        Printer.Outdent();
                        Printer.Print("}\n\n");


                    }

                    // generate constructor
                    Printer.Print(variables_, "$FactoryName$::$FactoryName$()\n{\n");
                    Printer.Indent();
                    Printer.Print("GOOGLE_PROTOBUF_VERIFY_VERSION;\nmemset(m_DecodeFuncArray, 0, sizeof(m_DecodeFuncArray));\n" +
                                  "Init();\n");
                    Printer.Outdent();
                    Printer.Print("}\n\n");

                    // generate destructor
                    Printer.Print(variables_, "$FactoryName$::~$FactoryName$()\n{\n");
                    Printer.Indent();
                    Printer.Print("google::protobuf::ShutdownProtobufLibrary();\n");
                    Printer.Outdent();
                    Printer.Print("}\n\n");

                    // generate initialization code
                    Printer.Print(variables_, "bool $FactoryName$::Init()\n{\n");
                    Printer.Indent();
                    foreach (DictionaryEntry de in message_info_from_name_)
                    {
                        MessageInfo info = (MessageInfo)de.Value;
                        if (info.func != null)
                            Printer.Print(String.Format("m_DecodeFuncArray[{0}] = &Decode{0};\n", de.Key));
                    }
                    Printer.Print("return true;\n");
                    Printer.Outdent();
                    Printer.Print("}\n\n");

                    // generate decode function code
                    Printer.Print(variables_, "void $FactoryName$::Decode(int nMsgID, const void * buf, int nSize, $ProcessorClass$ * pProc)\n{\n");
                    Printer.Indent();
                    Printer.Print(variables_, "if(nMsgID > $FileName$MaxMessageID || m_DecodeFuncArray[nMsgID] == NULL){\n");
                    Printer.Indent();
                    Printer.Print("return;\n");
                    Printer.Outdent();
                    Printer.Print("}\nm_DecodeFuncArray[nMsgID](buf, nSize, pProc);\n");
                    Printer.Outdent();
                    Printer.Print("}\n\n");


                    return true;
                }
            }
            catch (System.Exception e)
            {
                Console.WriteLine(e.Message);
                return false;
            }
        }

        public bool Run()
        {
            if (!GenerateHeaderFile())
                return false;

            if (!GenerateCPlusPlusFile())
                return false;

            return true;
        }
    }
}
